package gpc

import (
	"bufio"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"gitee.com/hz2769514387/gpc/system"
	"github.com/go-ini/ini"
)

//日志级别
type LOGLEVEL int8

const (
	LevelDebug LOGLEVEL = iota
	LevelInfo
	LevelWarning
	LevelError
	LevelClose
)

//日志对象
type LogTools struct {
	mutex      sync.Mutex
	year       int
	month      time.Month
	day        int
	logFile    *os.File
	logWritter *bufio.Writer
	exited     bool

	LogLevel        LOGLEVEL
	LogFileDirctory string
	LogFileName     string
	StdOut          int
}

var LogToolsDefault = new(LogTools)

//实现io.Writer
func (l *LogTools) Write(b []byte) (n int, err error) {
	return LogBytes(LevelInfo, b)
}

/*
InitLogger 初始化 log，返回关闭log的函数
调用示例：defer myutil.InitLogTools("xxx.ini")()
配置文件示例：
[Log]
LogFileDirctory=C:\LOG\LogDir
LogLevel=DEBUG
StdOut=1
*/
func InitLogTools(logconfigFile string) func() {
	//读取配置
	var cfg *ini.File
	var err error
	if filepath.IsAbs(logconfigFile) {
		cfg, err = ini.Load(logconfigFile)
	} else {
		cfg, err = ini.Load(path.Join(GetSelfFileDir(), logconfigFile))
	}
	if err != nil {
		fmt.Fprintln(os.Stdout, "ini.Load failed, err:", err)
		LogToolsDefault.LogFileDirctory = GetSelfFileDir()
		LogToolsDefault.LogLevel = LevelInfo
		LogToolsDefault.StdOut = 0
	} else {
		LogToolsDefault.LogFileDirctory = cfg.Section("Log").Key("LogFileDirctory").MustString(GetSelfFileDir())
		LogToolsDefault.StdOut = cfg.Section("Log").Key("StdOut").MustInt(0)
		LogLevel := cfg.Section("Log").Key("LogLevel").MustString("INFO")
		switch LogLevel[0] {
		case 'D', 'd':
			LogToolsDefault.LogLevel = LevelDebug
		case 'I', 'i':
			LogToolsDefault.LogLevel = LevelInfo
		case 'W', 'w':
			LogToolsDefault.LogLevel = LevelWarning
		case 'E', 'e':
			LogToolsDefault.LogLevel = LevelError
		case 'C', 'c':
			LogToolsDefault.LogLevel = LevelClose
		default:
			LogToolsDefault.LogLevel = LevelInfo
		}
	}
	LogToolsDefault.LogFileName = GetSelfFileName()

	//打开文件
	t := time.Now()
	reopenFile(&t)

	//hook panic
	err = system.StdErrorToFile(LogToolsDefault.logFile)

	//进程出入口日志
	Warningf("==========[start:%s]==================", GetSelfFileName())
	if err != nil {
		Warning("WARN:StdErrorToFile Fail:", err)
	}

	//定时flush
	LogToolsDefault.exited = false
	go func() {
		for {
			time.Sleep(1 * time.Second)

			LogToolsDefault.mutex.Lock()
			flushLog()
			if LogToolsDefault.exited {
				LogToolsDefault.mutex.Unlock()
				break
			}
			LogToolsDefault.mutex.Unlock()
		}
	}()
	return closeLogTools
}

// Debug
func Debugf(format string, v ...interface{}) {
	if LogToolsDefault.LogLevel > LevelDebug {
		return
	}
	LogString(LevelDebug, fmt.Sprintf(format, v...))
}

// Debug
func Debug(v ...interface{}) {
	if LogToolsDefault.LogLevel > LevelDebug {
		return
	}
	LogString(LevelDebug, fmt.Sprintln(v...))
}

// Infor
func Inforf(format string, v ...interface{}) {
	if LogToolsDefault.LogLevel > LevelInfo {
		return
	}
	LogString(LevelInfo, fmt.Sprintf(format, v...))
}

// Infor
func Infor(v ...interface{}) {
	if LogToolsDefault.LogLevel > LevelInfo {
		return
	}
	LogString(LevelInfo, fmt.Sprintln(v...))
}

// Warning
func Warningf(format string, v ...interface{}) {
	if LogToolsDefault.LogLevel > LevelWarning {
		return
	}
	LogString(LevelWarning, fmt.Sprintf(format, v...))
}

// Warning
func Warning(v ...interface{}) {
	if LogToolsDefault.LogLevel > LevelWarning {
		return
	}
	LogString(LevelWarning, fmt.Sprintln(v...))
}

// Error
func Errorf(format string, v ...interface{}) {
	if LogToolsDefault.LogLevel > LevelError {
		return
	}
	LogString(LevelError, fmt.Sprintf(format, v...))
}

// Error
func Error(v ...interface{}) {
	if LogToolsDefault.LogLevel > LevelError {
		return
	}
	LogString(LevelError, fmt.Sprintln(v...))
}

// Panic
func Panic(v ...interface{}) {
	logstr := fmt.Sprintln(v...)
	if LogToolsDefault.LogLevel != LevelClose {
		LogString(LevelError, logstr)
	}
	closeLogTools()
	panic(logstr)
}

//写入字符串
func LogString(writelevel LOGLEVEL, s string) {
	//写入前缀
	t := time.Now()
	var buff = logPrefixToBuf(writelevel, &t)

	//写入内容
	nonewline := (s[len(s)-1] != '\n')
	*buff = append(*buff, s...)
	if nonewline {
		*buff = append(*buff, '\n')
	}

	//实际执行写入
	LogToolsDefault.mutex.Lock()
	reopenFile(&t)
	if LogToolsDefault.logWritter != nil {
		LogToolsDefault.logWritter.Write(*buff)
	}
	LogToolsDefault.mutex.Unlock()

	if LogToolsDefault.StdOut != 0 {
		if nonewline {
			fmt.Fprintln(os.Stdout, s)
		} else {
			fmt.Fprint(os.Stdout, s)
		}
	}
}

//写入字节数组
func LogBytes(writelevel LOGLEVEL, b []byte) (n int, err error) {
	//写入前缀
	t := time.Now()
	var buff = logPrefixToBuf(writelevel, &t)

	//写入内容
	nonewline := (b[len(b)-1] != '\n')
	*buff = append(*buff, b...)
	if nonewline {
		*buff = append(*buff, '\n')
	}

	//实际执行写入
	LogToolsDefault.mutex.Lock()
	reopenFile(&t)
	if LogToolsDefault.logWritter != nil {
		n, err = LogToolsDefault.logWritter.Write(*buff)
	}
	LogToolsDefault.mutex.Unlock()

	if LogToolsDefault.StdOut != 0 {
		if nonewline {
			fmt.Fprintln(os.Stdout, string(b))
		} else {
			fmt.Fprint(os.Stdout, string(b))
		}
	}
	return n, err
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//重新打开文件
func reopenFile(pNowTime *time.Time) {
	tY, tM, tD := pNowTime.Date()
	if LogToolsDefault.logFile != nil &&
		LogToolsDefault.logWritter != nil &&
		tD == LogToolsDefault.day &&
		tM == LogToolsDefault.month &&
		tY == LogToolsDefault.year {
		LogToolsDefault.year, LogToolsDefault.month, LogToolsDefault.day = tY, tM, tD
		return
	}
	LogToolsDefault.year, LogToolsDefault.month, LogToolsDefault.day = tY, tM, tD

	//需要重新打开文件
	if LogToolsDefault.logFile != nil || LogToolsDefault.logWritter != nil {
		LogToolsDefault.logWritter.Flush()
		LogToolsDefault.logFile.Close()
	}

	// 判断是否有Director文件夹
	logFullPath := path.Join(LogToolsDefault.LogFileDirctory, pNowTime.Format("2006-01"), pNowTime.Format("2006-01-02")) + "/"
	if !IsExist(logFullPath) {
		os.MkdirAll(logFullPath, os.ModePerm)
	}
	logFullPath = path.Join(logFullPath, LogToolsDefault.LogFileName+"_"+pNowTime.Format("2006-01-02")+".log")

	var err error
	LogToolsDefault.logFile, err = os.OpenFile(logFullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("open log file failed, err:", err)
		return
	}
	if LogToolsDefault.logWritter == nil {
		LogToolsDefault.logWritter = bufio.NewWriterSize(LogToolsDefault.logFile, 1024*100)
	} else {
		LogToolsDefault.logWritter.Reset(LogToolsDefault.logFile)
	}
}

//Flush
func flushLog() {
	if LogToolsDefault.logWritter == nil {
		return
	}
	bufCnt := LogToolsDefault.logWritter.Buffered()
	if bufCnt == 0 {
		return
	}
	LogToolsDefault.logWritter.Flush()
}

// ExitLogger 退出log
func closeLogTools() {

	LogToolsDefault.mutex.Lock()
	if LogToolsDefault.exited {
		LogToolsDefault.mutex.Unlock()
		return
	}
	if LogToolsDefault.logWritter != nil {
		LogToolsDefault.mutex.Unlock()
		Warning("==========[ end ]==================")
		LogToolsDefault.mutex.Lock()
	}

	flushLog()
	LogToolsDefault.logWritter = nil
	LogToolsDefault.logFile = nil
	LogToolsDefault.exited = true

	LogToolsDefault.mutex.Unlock()
}

//写入前缀到buf
func logPrefixToBuf(writelevel LOGLEVEL, pNowTime *time.Time) *[]byte {
	//写入级别
	var buff []byte
	switch writelevel {
	case LevelDebug:
		buff = append(buff, 'D')
	case LevelInfo:
		buff = append(buff, 'I')
	case LevelWarning:
		buff = append(buff, 'W')
	case LevelError:
		buff = append(buff, 'E')
	default:
		buff = append(buff, '?')
	}
	buff = append(buff, ' ')

	//写入时间
	hour, min, sec := pNowTime.Clock()
	itoa(&buff, hour, 2)
	buff = append(buff, ':')
	itoa(&buff, min, 2)
	buff = append(buff, ':')
	itoa(&buff, sec, 2)
	buff = append(buff, '.')
	itoa(&buff, pNowTime.Nanosecond()/1e3, 6)
	buff = append(buff, ' ')

	//写入文件名和行号
	var file string
	var line int
	var ok bool
	_, file, line, ok = runtime.Caller(3)
	if !ok {
		file = "???"
		line = 0
	}
	for i := len(file) - 1; i > 0; i-- {
		if file[i] == '/' {
			file = file[i+1:]
			break
		}
	}
	buff = append(buff, file...)
	buff = append(buff, ':')
	itoa(&buff, line, 4)

	//文件名长度不够，补齐空格
	appendspace := 25 - len(file)
	if appendspace > 0 {
		appendstr := strings.Repeat(" ", appendspace)
		buff = append(buff, appendstr...)
	}
	buff = append(buff, ' ')
	return &buff
}

// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
func itoa(buf *[]byte, i int, wid int) {
	// Assemble decimal in reverse order.
	var b [20]byte
	bp := len(b) - 1
	for i >= 10 || wid > 1 {
		wid--
		q := i / 10
		b[bp] = byte('0' + i - q*10)
		bp--
		i = q
	}
	// i < 10
	b[bp] = byte('0' + i)
	*buf = append(*buf, b[bp:]...)
}
